1 /* 2 Copyright: Marcelo S. N. Mancini (Hipreme|MrcSnm), 2018 - 2021 3 License: [https://creativecommons.org/licenses/by/4.0/|CC BY-4.0 License]. 4 Authors: Marcelo S. N. Mancini 5 6 Copyright Marcelo S. N. Mancini 2018 - 2021. 7 Distributed under the CC BY-4.0 License. 8 (See accompanying file LICENSE.txt or copy at 9 https://creativecommons.org/licenses/by/4.0/ 10 */ 11 12 module hip.systems.compilewatcher; 13 14 version(Load_DScript): 15 import fswatch; 16 import std.concurrency; 17 import hip.util.path; 18 import std.datetime.stopwatch; 19 import core.time:Duration,dur; 20 import hip.util.system; 21 22 pragma(inline, true) private bool hasExtension(string file, ref immutable(string[]) extensions) 23 { 24 file = extension(file); 25 foreach(e;extensions) if(file == e) return true; 26 return false; 27 } 28 29 30 //Don't wait at all 31 private __gshared Duration timeout = dur!"msecs"(30);//Saves a lot of CPU Time 32 33 enum WatchFSDelay = 250; 34 35 void watchFs(Tid tid, string watchDir, 36 immutable(string[]) acceptedExtensions, immutable(string[]) ignoreDirs) 37 { 38 import core.thread.osthread:Thread; 39 bool shouldWatchFS = true; 40 FileWatch watcher = FileWatch(watchDir, true); 41 auto stopwatch = StopWatch(AutoStart.yes); 42 long lastTime = stopwatch.peek.total!"msecs"; 43 string lastEventPath; 44 while (shouldWatchFS) 45 { 46 receiveTimeout(timeout, 47 (bool exit) //The data is not important at all 48 { 49 shouldWatchFS = false; 50 }); 51 foreach (event; watcher.getEvents()) 52 { 53 // if (event.type == FileChangeEventType.create) Although creation is important, it only makes sense 54 //When it is imported by any module, so, modify only 55 if (event.type == FileChangeEventType.modify) 56 { 57 if(hasExtension(event.path,acceptedExtensions)) 58 { 59 lastTime = stopwatch.peek.total!"msecs"; 60 lastEventPath = event.path; 61 } 62 // send(tid, event.path); 63 } 64 // else if (event.type == FileChangeEventType.remove) Remove should not trigger compilation 65 } 66 if(lastEventPath && stopwatch.peek.total!"msecs" - lastTime > WatchFSDelay) 67 { 68 send(tid, lastEventPath); 69 lastEventPath = null; 70 } 71 // if (event.type == FileChangeEventType.rename) (Rename should not compile, it is not important) 72 // else if (event.type == FileChangeEventType.createSelf) The folder should not be created while watching 73 // else if (event.type == FileChangeEventType.removeSelf) It should not be removed while watching 74 75 } 76 stopwatch.stop(); 77 send(tid, true); 78 } 79 80 81 ///Use these property and function for not allocating closures everytime 82 private __gshared string compilerWatcherFileUpdate; 83 private void checkFileWatcher(string theFile) 84 { 85 compilerWatcherFileUpdate = theFile; 86 } 87 88 class CompileWatcher 89 { 90 string watchDir; 91 string[] acceptedExtensions; 92 string[] ignoredDirs; 93 void function(string fileName) handler; 94 Tid worker; 95 96 bool isRunning = false; 97 98 this(string watchDir, void function(string fileName) handler = null, 99 string[] acceptedExtensions = [], string[] ignoredDirs = []) 100 { 101 this.watchDir = watchDir; 102 foreach(ext; acceptedExtensions) 103 { 104 if(ext[0] != '.') 105 this.acceptedExtensions~= '.' ~ ext; 106 else 107 this.acceptedExtensions~=ext; 108 } 109 this.ignoredDirs = ignoredDirs; 110 this.handler = handler; 111 } 112 113 CompileWatcher run() 114 { 115 assert(!isRunning, "CompileWatcher is already running"); 116 // assert(handler != null, "CompileWatcher must have some handler before running"); 117 isRunning = true; 118 worker = spawn(&watchFs, thisTid, watchDir, 119 acceptedExtensions.idup, ignoredDirs.idup); 120 return this; 121 } 122 void stop() 123 { 124 if(isRunning) 125 send(worker, true); 126 } 127 128 string update() 129 { 130 if(isRunning) 131 { 132 receiveTimeout(timeout, &checkFileWatcher); 133 } 134 return compilerWatcherFileUpdate; 135 } 136 137 ~this() 138 { 139 stop(); 140 } 141 }